/*
 *
 * Copyright (c) 2023 - future
 * @author fzh
 * @email e_341877120_mail@163.com
 * @link https://mvnrepository.com/search?q=io.github.xrfzh.cn
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package cn.xrfzh.file.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.xrfzh.file.constants.MinioConstant;
import cn.xrfzh.file.mapper.SysUploadTaskMapper;
import cn.xrfzh.file.model.entity.SysUploadTask;
import cn.xrfzh.file.service.SysUploadTaskService;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.xrfzh.common.axios.AxiosEnum;
import cn.xrfzh.common.exception.MyCustomException;
import cn.xrfzh.file.model.dto.TaskInfoDto;
import cn.xrfzh.file.model.dto.TaskRecordDto;
import cn.xrfzh.file.model.param.InitTaskParam;
import cn.xrfzh.file.properties.MinioConfigurationProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 分片上传-分片任务记录(SysUploadTask)表服务实现类
 */
@Slf4j
@Service("sysUploadTaskService")
public class SysUploadTaskServiceImpl extends ServiceImpl<SysUploadTaskMapper, SysUploadTask>
		implements SysUploadTaskService {

	@Resource
	private AmazonS3 amazonS3Client;

	@Resource
	private MinioConfigurationProperties minioConfigurationProperties;

	@Resource
	private SysUploadTaskMapper sysUploadTaskMapper;

	@Override
	public SysUploadTask getByIdentifier(String identifier) {
		return sysUploadTaskMapper
				.selectOne(new QueryWrapper<SysUploadTask>().lambda().eq(SysUploadTask::getFileIdentifier, identifier));
	}

	@Override
	public TaskInfoDto initTask(InitTaskParam param) {
		Date currentDate = new Date();
		String bucketName = minioConfigurationProperties.getBucket();
		String fileName = param.getFileName();
		String suffix = fileName.substring(fileName.lastIndexOf("."));
		String key = StrUtil.format("{}/{}.{}", DateUtil.format(currentDate, "YYYY-MM-dd"), IdUtil.randomUUID(),
				suffix);
		String contentType = MediaTypeFactory.getMediaType(key).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
		ObjectMetadata objectMetadata = new ObjectMetadata();
		objectMetadata.setContentType(contentType);
		InitiateMultipartUploadResult initiateMultipartUploadResult = amazonS3Client.initiateMultipartUpload(
				new InitiateMultipartUploadRequest(bucketName, key).withObjectMetadata(objectMetadata));
		String uploadId = initiateMultipartUploadResult.getUploadId();

		SysUploadTask task = new SysUploadTask();
		int chunkNum = (int) Math.ceil(param.getTotalSize() * 1.0 / param.getChunkSize());
		task.setBucketName(minioConfigurationProperties.getBucket()).setChunkNum(chunkNum)
				.setChunkSize(param.getChunkSize()).setTotalSize(param.getTotalSize())
				.setFileIdentifier(param.getIdentifier()).setFileName(fileName).setObjectKey(key).setUploadId(uploadId)
				.setFileUrl(minioConfigurationProperties.getEndpoint() + "/" + minioConfigurationProperties.getBucket()
						+ "/" + key);
		sysUploadTaskMapper.insert(task);
		return new TaskInfoDto().setFinished(false).setTaskRecord(TaskRecordDto.convertFromEntity(task))
				.setPath(getPath(bucketName, key));
	}

	@Override
	public String getPath(String bucket, String objectKey) {
		return StrUtil.format("{}/{}/{}", minioConfigurationProperties.getEndpoint(), bucket, objectKey);
	}

	@Override
	public TaskInfoDto getTaskInfo(String identifier) {
		SysUploadTask task = getByIdentifier(identifier);
		if (task == null) {
			return null;
		}
		TaskInfoDto result = new TaskInfoDto().setFinished(true).setTaskRecord(TaskRecordDto.convertFromEntity(task))
				.setPath(getPath(task.getBucketName(), task.getObjectKey()));
		boolean doesObjectExist = amazonS3Client.doesObjectExist(task.getBucketName(), task.getObjectKey());
		if (!doesObjectExist) {
			// 未上传完，返回已上传的分片
			ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(), task.getObjectKey(),
					task.getUploadId());
			PartListing partListing = amazonS3Client.listParts(listPartsRequest);
			result.setFinished(false).getTaskRecord().setExitPartList(partListing.getParts());
		}
		return result;
	}

	@Override
	public String genPreSignUploadUrl(String bucket, String objectKey, Map<String, String> params) {
		Date currentDate = new Date();
		Date expireDate = DateUtil.offsetMillisecond(currentDate, MinioConstant.PRE_SIGN_URL_EXPIRE.intValue());
		GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, objectKey)
				.withExpiration(expireDate).withMethod(HttpMethod.PUT);
		if (params != null) {
			params.forEach(request::addRequestParameter);
		}
		URL preSignedUrl = amazonS3Client.generatePresignedUrl(request);
		return preSignedUrl.toString();
	}

	@Override
	public void merge(String identifier) {
		SysUploadTask task = getByIdentifier(identifier);
		if (task == null) {
			throw new MyCustomException(AxiosEnum.SLICE_TASK_NOT_EXISTS);
		}
		ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(), task.getObjectKey(),
				task.getUploadId());
		PartListing partListing = amazonS3Client.listParts(listPartsRequest);
		List<PartSummary> parts = partListing.getParts();
		if (!task.getChunkNum().equals(parts.size())) {
			// 已上传分块数量与记录中的数量不对应，不能合并分块
			throw new MyCustomException(AxiosEnum.PLEASE_TRY_UPLOAD_AGAIN);
		}
		CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest()
				.withUploadId(task.getUploadId()).withKey(task.getObjectKey()).withBucketName(task.getBucketName())
				.withPartETags(parts.stream()
						.map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag()))
						.collect(Collectors.toList()));
		amazonS3Client.completeMultipartUpload(completeMultipartUploadRequest);
		log.info(AxiosEnum.UPLOAD_SUCCESS.getMsg());
	}

}
